/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       adventure_map_window.h

	$Header: /game/adventure_map_window.h $

	$NoKeywords: $

 ************************************************************************/

// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "adventure_map_window.h"

#include "abstract_adv_object.h"
#include "adv_map_point.h"
#include "adventure_frame.h"
#include "adventure_path_finder.h"
#include "adventure_spell_targeter.h"
#include "actor.h"
#include "adventure_map.h"
#include "animated_window.h"
#include "animation_cache.h"
#include "army.h"
#include "army_mover.h"
#include "bitmap_group.h"
#include "bitmap_group_cache.h"
#include "bitmap_group_window.h"
#include "bound_handler.h"
#include "convert_16_bit.h"
#include "external_string.h"
#include "footprint.h"
#include "game_time.h"
#include "mouse_event.h"
#include "mouse_window.h"
#include "options.h"
#include "platform.h"
#include "player.h"
#include "simple_dialog.h"

static t_bitmap_group_cache const k_activate_boat_cursor( "cursor.boat_activate" );
static t_bitmap_group_cache const k_activate_cursor( "cursor.activate" );
static t_bitmap_group_cache const k_anchor_cursor( "cursor.anchor" );
static t_bitmap_group_cache const k_attack_cursor( "cursor.attack" );
static t_bitmap_group_cache const k_move_boat_cursor( "cursor.boat_move" );
static t_bitmap_group_cache const k_move_cursor( "cursor.move" );
static t_bitmap_group_cache const k_transfer_cursor( "cursor.exchange" );
extern t_animation_cache const k_spell_cursor;

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
t_adventure_map_window::t_adventure_map_window( t_screen_rect const& rect, 
												t_adventure_frame* parent )
                      : t_adventure_map_window_base ( rect, parent, parent )
{
	m_dragging = false;

	// set animation
	enable_animation( show_adventure_animations() );
	// create various cursors.

	m_move_cursor.create( k_move_cursor.get() );
	m_move_boat_cursor.create( k_move_boat_cursor.get() );
	m_anchor_cursor.create( k_anchor_cursor.get() );
	m_activate_cursor.create( k_activate_cursor.get() );
	m_activate_boat_cursor.create( k_activate_boat_cursor.get() );
	m_transfer_cursor.create( k_transfer_cursor.get() );
	m_attack_cursor.create( k_attack_cursor.get() );
	m_normal_cursor.cursor = parent->get_cursor( false );

	m_spell_cursor_animation = k_spell_cursor.get();
	t_screen_rect cursor_rect = m_spell_cursor_animation->get_rect();
	m_spell_cursor = new t_mouse_window;
	m_spell_cursor->set_visible( false );
	m_spell_cursor->set_hot_spot( cursor_rect.size() / 2 );
	t_window* animated_window = new t_animated_window( m_spell_cursor_animation, t_screen_point(0,0), 100, m_spell_cursor );

	t_window::init( rect );
	t_map_renderer::move_view( t_screen_point(0,0), t_screen_point(get_width(), get_height()) );
	update_cursor();
}

t_adventure_map_window::~t_adventure_map_window()
{
	if (m_map != 0)
		m_map->attach( 0 );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::paint( t_paint_surface& paint_surface )
{
	update( paint_surface.get_rect() );
	paint_surface.draw( t_screen_point(0,0), *m_back_buffer, m_back_buffer->get_rect() );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::on_size_change( t_screen_rect const& rect )
{
	bool size_changed = (m_back_buffer == 0);
	
	if (get_screen_rect().height() != rect.height() || get_screen_rect().width() != rect.width())
		size_changed = true;

	if (size_changed)
	{
		t_screen_point view_pos = get_view_pos();

		t_map_renderer::move_view( view_pos, t_screen_point( rect.width(), rect.height() ) );
		get_frame()->resize_view(rect);
	}
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
	// Member functions
t_abstract_bitmap16_ptr t_adventure_map_window::create_back_buffer( t_screen_point const & view_size )
{
	m_back_buffer = new t_memory_bitmap16( view_size.x, view_size.y );
	return m_back_buffer;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::on_rects_dirtied( std::vector< t_screen_rect > const & dirtied_rects )
{
	int i;

	for (i = 0; i < dirtied_rects.size(); i++)
		t_window::invalidate( dirtied_rects[i] );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::on_view_moved( int new_view_level, t_screen_point const & new_view_pos, 
		                                    t_screen_point const & new_view_size )
{
	get_frame()->resize_view(t_screen_rect(new_view_pos, new_view_size));
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::drag( t_screen_point point )
{
	t_screen_point delta = m_drag_start - point;
	t_screen_point new_point = get_view_pos() + delta;

	m_drag_start = point;

	move_view( new_point );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::left_button_down( t_mouse_event const& event )
{
	if (capture_mouse())
	{
		m_dragging = true;
		m_drag_start = event.screen_point;
	}
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::left_button_up( t_mouse_event const& event )
{
	assert( m_frame );

	if (m_dragging)
	{
		drag( event.screen_point );
		release_mouse();
		m_dragging = false;
	}

	if (m_object_mover != 0)
	{
		halt_movement();
		return;
	}

	if ( m_spell_targeter != 0 )
	{
		m_spell_targeter->cast_spell( *this, event.client_point );
		clear_spell_targeter();
		return;
	}

	t_army* army = m_frame->get_selected_army();
	if (army == 0)
		return;

	// otherwise, move the temporary hero.
	t_adventure_path_point path_point;
	t_adventure_path path;

	// abort if this is not a valid location.
	if (!get_path_point( event.client_point, path_point ))
		return;

    if (!(show_move_path()))
    {
	    m_frame->get_path_finder()->get_path( path_point, path );
		army->set_path( path );
    }

	// check if the army has a valid path, and the path matches this point.
	if (army->get_destination() == path_point)
	{
		// start army moving.
		m_frame->start_army_moving( army );
		return;
	}

	if (!m_frame->get_path_finder()->get_path( path_point, path ))
	{
		army->set_path( path );
		return;
	}
	army->set_path( path );
	m_map->display_path( path, army->get_movement() );
	m_frame->update_buttons();
}

// ------------------------------------------------------------------------
// window to display an adventure map
// double click - on an adventure object?
// ------------------------------------------------------------------------
void t_adventure_map_window::left_double_click( t_mouse_event const& event )
{
	// check if we should not respond to the double click
	if (m_dragging)
		return;

	t_army* army = m_frame->get_selected_army();

	t_adventure_object* object = get_object( event.client_point );

	if (army != 0)
	{
		if (m_object_mover != 0)
			return;

		// See if the currently selected army was double-clicked
		if (army == object) 
		{
			army->left_double_click( event, m_frame );
			return;
		}

		// abort if this is not a valid location.
		t_level_map_point_2d map_point;
		if ( tile_hit_test( event.client_point, map_point ) )
		{
			map_point.level = get_view_level();

			// check if the army has a valid path, and the path matches this point.
			if (!army->get_path().empty() && army->get_destination() == map_point)
			{
				// Since the path isn't empty, the next step cost should be non-zero, right?
				// If not, we need to check for that case, cause we won't move if we have zero movement left.
				assert( army->get_next_step_cost() );

				// start army moving.
				if (army->get_movement() >= army->get_next_step_cost())
					return;
			}
		}
	}

	// If we didn't intiate movement, but we did hit a map object, activate it
	if (object != 0)
		object->left_double_click( event, m_frame );
}

static t_external_string const k_off_the_map_description( "off_the_map.description" );
static t_external_string const k_unexplored_description( "unexplored.description" );

// ------------------------------------------------------------------------
// window to display an adventure map
// right click - on an adventure object?
// ------------------------------------------------------------------------
void t_adventure_map_window::right_button_up( t_mouse_event const& event )
{
	t_adventure_object* object = get_object( event.client_point );
	if (object != 0)
	{
		object->right_click( event, m_frame );
	}
	else
	{
		// Display description of terrain 
		std::string text;

		t_adv_map_point map_point;
		map_point.level = get_view_level();
		map_point.on_bridge = false;

		if (!tile_hit_test( event.client_point, map_point ))
		{
			text = k_off_the_map_description;
		}
		else
		{
			t_adventure_tile const & tile = m_map->get_adv_tile( map_point );

			if ( tile.get_visibility( get_team_view() ) == k_tile_never_explored )
			{
				text = k_unexplored_description;
			}
			else if ( tile.get_road_type() != k_road_none )
			{
				text = get_road_type_description( tile.get_road_type() );
			}
			else if ( tile.get_rock_terrain_type() != k_rock_terrain_none )
			{
				text = get_rock_terrain_type_description( tile.get_rock_terrain_type() );
			}
			else
			{
				text = get_terrain_type_description( tile.get_terrain(), tile.get_terrain_subtype() );
			}
		}

		show_popup_text( text, event.screen_point );
	}
}

// ------------------------------------------------------------------------
// window to display an adventure map
// get correct cursor for location.
// ------------------------------------------------------------------------
t_bitmap_cursor const& 
t_adventure_map_window::get_move_cursor( t_adventure_path_point const& point ) const
{
	t_army*	            army = m_frame->get_selected_army();
	t_adventure_object* object = m_map->get_trigger_object( point.last_point, point.direction,
		                                                   army );
	if ( object != 0 && !hidden_by_fog_of_war( *object ) )
		return object->get_cursor( *this, army );

	if (point.visible_enemy_adjacent)
		return m_attack_cursor;
	if (use_boat_cursors() && get_map().is_land( point ))
		return m_anchor_cursor;
	return get_move_cursor();
}

// ------------------------------------------------------------------------
// window to display an adventure map
// get correct cursor for location.
// ------------------------------------------------------------------------
t_mouse_window* t_adventure_map_window::get_new_cursor( t_screen_point screen_point ) const
{
	if ( m_object_mover != 0 )
		return m_normal_cursor.cursor;

	if ( m_spell_targeter != 0 )
	{
		if ( m_spell_targeter->is_valid_target( *this, screen_point ) )
			return m_spell_cursor;
		return m_normal_cursor.cursor;
	}

	t_army*	army = m_frame->get_selected_army();
	if (army == 0)
		return m_normal_cursor.cursor;

	int speed = army->get_max_movement();

	if (speed == 0)
		return m_normal_cursor.cursor;

	t_adventure_path_point point;

	if (!get_path_point( screen_point, point ))
		return m_normal_cursor.cursor;

	int turns;
	
	if (point.move_cost > army->get_movement())
		turns = (point.move_cost + speed - army->get_movement() - 1) / speed;
	else
		turns = 0;

	t_bitmap_cursor const& cursor = get_move_cursor( point );

	if (turns >= cursor.frames->get_frame_count() - 1)
		turns = cursor.frames->get_frame_count() - 2;
	if (turns < 0)
		turns = 0;
	cursor.frames->set_frame( turns );
	return cursor.cursor;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// determine correct cursor to display
// ------------------------------------------------------------------------
void t_adventure_map_window::update_cursor( t_screen_point point )
{
	t_mouse_window* cursor = get_new_cursor( point );

	if (cursor != get_cursor( false ))
		set_cursor( cursor );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// determine correct cursor to display
// ------------------------------------------------------------------------
void t_adventure_map_window::update_cursor()
{
	t_screen_point point = get_mouse_position( this );
	t_screen_rect  rect = get_client_rect();

	if (!is_point_in_rect( point, rect ))
		return;
	update_cursor( point );
}


// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::mouse_move( t_mouse_event const& event )
{
	if (!m_dragging)
	{
		if ( get_mouse_position( this ) != event.client_point )
			return;
		update_cursor( event.client_point );
		return;
	}

	if (!event.left_button)
	{
		release_mouse();
		m_dragging = false;
		return;
	}

	// check event message against current mouse position.
	if ( get_mouse_position( this ) != event.client_point )
		return;

	drag( event.screen_point );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// determine if a click is appropriate for the top tile of a bridge span
// ------------------------------------------------------------------------
bool t_adventure_map_window::get_bridge_point( t_screen_point const& screen_point,
										       t_adv_map_point& map_point ) const
{
	t_adventure_map_object_id id = adv_object_hit_test( screen_point );
	
	if (id == t_adventure_map::k_invalid_object_id )
		return false;

	t_adventure_object* bridge;

	bridge = &m_map->get_adv_object( id );
	t_adv_map_point point = bridge->get_position();
	if (!bridge->is_bridge() && !point.on_bridge )
		return false;

	t_footprint const&  footprint = bridge->get_footprint();
	t_map_point_2d	    footprint_size = footprint.get_size();
	
	map_point = point;
	map_point.on_bridge = true;

	// handle bridge pieces that are 2x1
	if (footprint_size.column > 1 || footprint_size.row > 1)
	{
		t_adv_map_point		division_point;
		int					division_screen_point;

		division_point = map_point;
		if (footprint_size.column > 1)
			++division_point.column;
		else
			++division_point.row;
		division_screen_point = m_map->get_screen_point( division_point ).x;
		division_screen_point -= get_view_pos().x;
		if (footprint_size.column > 1)
		{
			if (screen_point.x >= division_screen_point)
				map_point = division_point;
		}
		else if (screen_point.x < division_screen_point)
		{
			map_point = division_point;
		}
	}
	return true;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
bool t_adventure_map_window::get_path_point( t_adv_map_point const& map_point,
											 t_adventure_path_point& path_point ) const
{
	t_army*	army = m_frame->get_selected_army();

	if (army == 0)
		return false;

	if (!m_map->is_valid( map_point ))
		return false;
	// check if it's legal to enter this square.
	if (m_map->is_blocked( map_point ) && !m_map->is_trigger( map_point ))
		return false;

	// check if this is a horse, and we can't enter water, and there is no boat here.
	if (!army->is_boat() && !map_point.on_bridge && !m_map->is_land( map_point )
		&& !m_map->is_trigger( map_point ))
		return false;

	t_adventure_path_finder*		path_finder = m_frame->get_path_finder();
	t_adventure_path_data const*	path_data = &path_finder->get_data( map_point );
	t_adv_map_point					point = map_point;

	if ( path_data->visible_enemy_adjacent && !m_map->is_trigger( map_point ) )
	{
		t_direction			direction;
		t_army*				enemy;
		t_adventure_object* object;

		direction = path_data->visible_enemy_direction;
		object = m_map->get_trigger_object( map_point, direction, army );
		enemy = dynamic_cast<t_army*>( object );
		if (enemy != 0)
		{
			point += get_direction_offset( direction );
			path_data = &path_finder->get_data( point );
		}
	}
	
	path_point = t_adventure_path_point( point, *path_data );
	return path_point.visited && path_point.move_cost > 0;
}

// ------------------------------------------------------------------------
// do a hit test, but also include all cells of footprint.
// ------------------------------------------------------------------------
t_adventure_map::t_object_id 
t_adventure_map_window::adv_object_hit_test( t_screen_point const& screen_point ) const
{
	t_object_id result = t_map_renderer::adv_object_hit_test( screen_point );

	if (result != t_adventure_map::k_invalid_object_id)
		return result;

	t_adv_map_point map_point;
	map_point.level = get_view_level();
	map_point.on_bridge = false;

	if (!tile_hit_test( screen_point, map_point ))
		return t_adventure_map::k_invalid_object_id;

	t_adventure_tile const& tile = m_map->get_adv_tile( map_point );
	if ( tile.get_visibility( get_team_view() ) == k_tile_never_explored )
		return t_adventure_map::k_invalid_object_id;

	int				count = tile.get_intersecting_object_count();
	int				i;
	t_object_id		id;
	t_adv_map_point	trigger_point;

	for (i = 0; i < count; ++i)
	{
		id = tile.get_intersecting_object_id(i);

		t_adventure_object const& object = m_map->get_adv_object( id );

		if (object.get_trigger_cell( trigger_point ) && !object.get_position().on_bridge)
			return id;
	}
	return result;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
bool t_adventure_map_window::get_path_point( t_screen_point const& screen_point,
											 t_adventure_path_point& path_point ) const
{
	t_army*	army = m_frame->get_selected_army();

	if (army == 0)
		return false;

	t_adv_map_point map_point;

	// Get a map point from the screen point and if that is a valid path point
	// return that.  If the selected army is not a boat get_bridge_point()
	// needs to be checked before tile_hit_test().
	map_point.level = get_view_level();
	map_point.on_bridge = false;
	if (	(	( !army->is_boat() && get_bridge_point( screen_point, map_point ) )
			||	tile_hit_test( screen_point, map_point ) )
		&&	get_path_point( map_point, path_point ) )
		return true;

	// Now see if we are pointing at an object
	t_adventure_map::t_object_id object_id = adv_object_hit_test( screen_point );
	if ( object_id == t_adventure_map::k_invalid_object_id )
		return false;

	// If this object has any reachable triggerable cells, return the one with
	// the least costly path.
	t_adventure_object const & object = m_map->get_adv_object( object_id );
	t_map_point_2d object_top = object.get_position();
	t_map_point_2d object_bottom = object_top + object.get_footprint().get_size();

	// Clamp object area to map
	if ( object_top.row < 0 )
		object_top.row = 0;
	if ( object_top.column < 0 )
		object_top.column = 0;
	int map_size = m_map->get_size();
	if ( object_bottom.row > map_size )
		object_bottom.row = map_size;
	if ( object_bottom.column > map_size )
		object_bottom.column = map_size;

	bool result = false;

	// Scan each cell covered by this object looking for triggers
	map_point.on_bridge = false;
	for ( map_point.row = object_top.row; map_point.row < object_bottom.row; ++map_point.row )
	{
		int row_start = m_map->get_row_start( map_point.row );
		if ( row_start < object_top.column )
			row_start = object_top.column;
		int row_end = m_map->get_row_end( map_point.row );
		if ( row_end > object_bottom.column )
			row_end = object_bottom.column;

		for ( map_point.column = row_start; map_point.column < row_end; ++map_point.column )
		{
			// Skip cells for which this object is not the trigger
			if ( m_map->get_trigger_object( map_point, army ) != &object )
				continue;

			t_adventure_path_point temp_path_point;
			if (	get_path_point( map_point, temp_path_point )
				&&	( !result || temp_path_point.move_cost < path_point.move_cost ) )
			{
				path_point = temp_path_point;
				result = true;
			}
		}
	}

	return result;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
t_adventure_object* t_adventure_map_window::get_object( t_screen_point point ) const
{
	t_adventure_path_point path_point;

	if (get_path_point( point, path_point ))
	{
		t_army*				army = m_frame->get_selected_army();
		t_adventure_object* object;
					
		object = m_map->get_trigger_object( path_point.last_point, path_point.direction,
                                            army );
		if ( object != 0 && !hidden_by_fog_of_war( *object ) )
			return object;
	}

	t_adventure_map_object_id id = adv_object_hit_test( point );
	
	if (id == t_adventure_map::k_invalid_object_id )
		return 0;

	return &m_map->get_adv_object( id );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
bool t_adventure_map_window::is_visible( t_screen_point const& point ) const
{
	t_screen_point view_size = get_view_size();
	t_screen_point view_pos = get_view_pos();
	t_screen_rect  rect( view_pos, view_size );

	rect.top += 64 * 4;
	rect.left += 64 * 4;
	rect.right -= 64 * 4;
	rect.bottom -= 64 * 4;

	if (rect.height() < 64)
	{
		int center = (rect.top + rect.bottom) / 2;

		rect.top = center - 32;
		rect.bottom = center + 32;
	}
	if (rect.width() < 64)
	{
		int center = (rect.left + rect.right) / 2;
		
		rect.left = center - 32;
		rect.right = center + 32;
	}
	return is_point_in_rect( point, rect );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
bool t_adventure_map_window::is_visible( t_level_map_point_2d const& point ) const
{
	t_screen_point screen_point = m_map->get_screen_point( point );

	screen_point.y -= m_map->get_tile_vertex( point ).get_height();
	return is_visible( screen_point );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
std::string t_adventure_map_window::get_help_text( t_screen_point point ) const
{
	static t_external_string const k_text_adventure_coordinates( "adventure_coordinates.misc" );
	static t_external_string const k_text_unexplored( "unexplored.misc" );

	std::string      final_text;
	std::string		 position_text;
	bool			 show_coordinates = show_adventure_coordinates();

	t_adv_map_point map_point;
	bool			explored = false;
	bool			open_tile = false;
	int				team_view = get_team_view();
	t_army*			army = m_frame->get_selected_army();
	
	map_point.level = get_view_level();
	map_point.on_bridge = false;
	if ( army == 0 || !army->is_boat() )
		open_tile = get_bridge_point( point, map_point );
	if (!open_tile)
		open_tile = tile_hit_test( point, map_point );
	if ( open_tile )
	{
		if ( team_view >= 0 )
		{
			t_abstract_adventure_tile const & tile = m_map->get_tile( map_point );
			if ( tile.get_visibility( team_view ) != k_tile_never_explored )
				explored = true;
		}
		else
			explored = true;
	}

	position_text = format_string( k_text_adventure_coordinates, map_point.row, map_point.column );

	t_adventure_object const* object = get_object( point );

	if ( object && object->is_visible_to( team_view ) )
    {
		std::string const & object_balloon_help = object->get_balloon_help();
		if ( !object_balloon_help.empty() )
		{
			if ( show_coordinates )
				final_text = position_text;
			final_text += object_balloon_help;
			return final_text;
		}
    }

	if ( !explored )
	{
		final_text = k_text_unexplored;
	}
	else
	{
		if ( show_coordinates )
			final_text = position_text;

		t_adventure_tile const & tile = m_map->get_adv_tile( map_point );

		if ( tile.get_road_type() != k_road_none )
		{
			final_text += get_road_type_name( tile.get_road_type() );
		}
		else if ( tile.get_rock_terrain_type() != k_rock_terrain_none )
		{
			final_text += get_rock_terrain_type_name( tile.get_rock_terrain_type() );
		}
		else
		{
			final_text += get_terrain_type_name( tile.get_terrain(), tile.get_terrain_subtype() );
		}
	}

    return final_text;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::on_close()
{
	if (m_map != 0)
		m_map->attach( 0 );
	m_object_mover = 0;
	m_map = 0;
	enable_animation( false );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// halt the currently moving hero.
// ------------------------------------------------------------------------
bool t_adventure_map_window::halt_movement()
{
	if (m_attacking_enemy != 0)
		return false;
	if (m_object_mover != 0)
	{
		t_counted_ptr<t_army_mover> mover = m_object_mover;

		m_object_mover = 0;
		get_frame()->refresh_path();
		mover->cancel_move(); // may re-instate a move due to enemy attack.
	}
	m_frame->update_buttons();
	return true;
}

// ------------------------------------------------------------------------
// attempt to retreat 
// ------------------------------------------------------------------------
static bool attempt_retreat( t_army* army, t_direction direction, 
							 t_adventure_map_window* map_window )
{
	t_adventure_map*	map = army->get_map();
	t_adv_map_point		point = army->get_position();

	point += get_direction_offset( direction );
	if (!map->is_valid( point ))
		return false;
	
	t_adventure_tile const& tile = map->get_adv_tile( point );

	if (tile.blocks_army( *army, *map, k_path_search_enemy_activation))
		return false;

	map_window->move_enemy_army( army, point );
	return true;
}

void t_adventure_map_window::run_object_mover( t_counted_ptr<t_army_mover> mover,
											   bool allow_cancel )
{
	// a little paranoid self-reference if this window is closed during the move.
	t_window_ptr					ref = this;
	t_counted_ptr<t_adventure_map>	map = m_map;

	clear_spell_targeter();
	m_object_mover = mover;
	m_frame->reset_pathfinder();

	// override all the game's input
	// any mouse or key press will abort the move
	t_override_input input_handler;

	if (allow_cancel)
	{
		// override all the game's input
		// any mouse or key press will abort the move
		t_handler cancel_handler = bound_handler( *mover, &t_army_mover::cancel_move );

		input_handler.set_input_handler( cancel_handler );
	}
	// run one cycle before anything else happens.
	mover->on_idle();
	while ( !map->is_game_over() && !mover->is_done()) 
	{
		process_message();
	}
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::check_enemy_attack( t_army* army, t_handler enemy_attack_started_handler )
{
	if (m_attacking_enemy != 0)
		return;

	if (army->empty() || army->is_graveyard())
		return;

	t_adventure_path_finder*			 frame_path_finder;
	t_owned_ptr<t_adventure_path_finder> local_finder;
	t_adventure_path_finder*			 path_finder;
	t_adv_map_point						 point;

	point = army->get_position();
	frame_path_finder = m_frame->get_path_finder();
	if (frame_path_finder->get_army() == army)
		path_finder = frame_path_finder;
	else
	{
		local_finder.reset( new t_adventure_path_finder( *m_map ));
		path_finder = local_finder.get();
		path_finder->set_army( army );
	}

	if ( !path_finder->get_data( point ).actual_enemy_adjacent )
		return;

	// find adjacent army
	t_direction	direction;
	t_army*		enemy;

	enemy = find_enemy( *m_map, *path_finder, point, *army, &direction );
	assert( enemy != 0 );
	if (enemy == 0)
		return;

	t_level_map_point_2d enemy_position = enemy->get_position();

	if ((abs( enemy_position.row - point.row ) > 1 
		 || abs(enemy_position.column - point.column ) > 1)
		&& enemy->ai_value() * 5.0 <= army->ai_value())
	{
		if (attempt_retreat( enemy, direction, this ))
			return;
	}

	// suppress activation if the enemy is hidden, except for neutrals
	if (enemy->get_owner_number() >= 0)
	{
		int team;

		if (army->get_owner_number() < 0)
			team = -1;
		else
			team = army->get_owner()->get_team();
		if (!enemy->is_visible_to( team ))
			return;
	}

	// move enemy to attack
	t_adventure_path path;

	local_finder.reset( new t_adventure_path_finder( *m_map ));
	path_finder = local_finder.get();
	path_finder->set_army( enemy );

	path_finder->set_path_type( k_path_search_enemy_activation );
	if (!path_finder->get_path( army->get_position(), path ))
		return;

	// Allow caller to close windows and the like before combat actually starts
	enemy_attack_started_handler();

	t_counted_ptr<t_army_mover> mover;
	t_window_ptr				ref = this;

	m_attacking_enemy = enemy;
	enemy->set_path( path );
	m_map->clear_path();
	m_map->clear_selection();
	enemy->set_in_combat( true ); // make visible regardless of stealth as it attacks.
	run_object_mover( new t_enemy_army_attack( this, enemy, army ), false );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::clear_enemy_attack()
{
	m_attacking_enemy = 0;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::move_enemy_army( t_army* enemy,
											  t_adv_map_point const& point )
{
	// move enemy to attack
	t_adventure_path        path;
	t_adventure_path_finder path_finder( *m_map );

	m_attacking_enemy = enemy;
	path_finder.set_army( enemy );
	path_finder.set_path_type( k_path_search_enemy_activation );
	if (!path_finder.get_path( point, path ))
		return;

	enemy->set_path( path );
	m_map->clear_path();
	m_map->clear_selection();
	run_object_mover( new t_enemy_army_mover( this, enemy ), false );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// get the army, if any, which is currently responding to a move.
// ------------------------------------------------------------------------
t_army* t_adventure_map_window::get_attacking_army() const
{
	return m_attacking_enemy;
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
bool t_adventure_map_window::hidden_by_fog_of_war( t_adventure_object const & object ) const
{
	return object.hidden_by_fog_of_war( get_team_view() );
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::clear_spell_targeter()
{
	if ( m_spell_targeter.get() != 0 )
	{
		m_spell_targeter = 0;
		update_cursor();
	}
}

// ------------------------------------------------------------------------
// window to display an adventure map
// ------------------------------------------------------------------------
void t_adventure_map_window::set_spell_targeter( t_spell_targeter_ptr new_targeter )
{
	assert( new_targeter.get() != 0 );
	assert( m_object_mover.get() == 0 );
	m_spell_targeter = new_targeter;
	update_cursor();
}

bool t_adventure_map_window::use_boat_cursors() const
{
	t_army* army = m_frame->get_selected_army();
	
	if (army == 0)
		return false;
	return army->is_boat();
}
